#! /usr/bin/env python
# -*- coding: UTF-8 -*-/**-
# Copyright (C) 2008 2009 Robert Spilleboudt
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#_______________________________________________________________________________________________________
# here are all the data processing, without the GUI interface, for the  RCCL Simulator and preprocessor
SIMUL =1 # normal 0 other is a debug level
TOINT=100  # conversion des variables en integer -> rccl.h
TRAVEL =False   # 0=normal, no simulation of the TRAVEL instruction
#SERIAL_PORT="/dev/ttyUSB0"provisoirement supprimé ?
#BAUD=9600 #-> util/uart.c
#BAUD=19200 #max baud for my adapters (Belkin...) AND low frequency error with the atmega128 specs
MAX_LINE=100 #-> rccl.h
EOL="\n"
SERIAL_COMMAND="$" # special character for a command coming from the microprocessor -> rccl.h
#OPCODE is the internal opcode for the microprocessor         
OPCODE={ "PHASE": "P", "SLOW":"W",  "INIT":"I", "ADD":"+", "+":"+","SUB":"-","-":"-","=":"=", "RAL":"R","EXP":"E",\
    "DTRIM1":"T", "DTRIM2":"T","DTRIM3":"T","DTRIM4":"T", \
    "MULT":"*","*":"*","MULT%":"%",      "OUT":"O","OUTN":"N","CURVE":"C","LINE":"L",\
    "ADDS":"S","ADDAS":"A",   "SUBS":"s","SUBAS":"a","SERV":"r",\
    "AND":"0",  "OR":"1","NOT":"2","COMP":"3","COMPH":"6", \
    "NUM":"4","NUMT":"5", "MEM":"7",  "IF":"8","ENDIF":"9", "CHR+":"x","CHR-":"y", "BEEP":"z" , "BREAK":"B", "UPD":"U", \
    "TRAVEL":" ", "FALSE":"f", "TRUE":"t"}  #cancelled:,"TRIM":"T","TRIMS":"u", "TRIMA":"t"
# OUTVAR = type of output var
#    typeNum as defined in var 0=logical 1=numerical
#    -1 no output var
# no first parameter -> look at cleanline()
OUTVAR={"MODUL":-1,  "PHASE": 0,"SLOW":-1,"EQU":-1, "INIT":1,  "ADD":1, "+":1,"SUB":1,"-":1,"=":1,\
    "DTRIM1":-1, "DTRIM2":-1, "DTRIM3":-1, "DTRIM4":-1, \
    "EXP":1,"MULT":1,"*":1,"MULT%":1,"OUT":1,"OUTN":1,"CURVE":1,"LINE":1,"ADDS":1,"ADDAS":1,"SUBS":1,"SUBAS":1,"SERV":1,\
    "AND":0,       "OR":0,"NOT":0,"COMP":0,"NUM":1,"NUMT":1, "MEM":0,   "IF":-1,"ENDIF":-1,\
     "CHR+":1, "CHR-":1, "BEEP":-1, "BREAK":-1, "UPD":-1,  "TRAVEL":1, "FALSE":0, "TRUE":0} #cancelled "TRIM":1, "TRIMS":1,"TRIMA":1,
# normal outvar are with typeOrig=3
# State variables are not initialized to zero : they are used to provide a time factor. The result of one operation is depending
# on the input arguments AND the former value of the output variable
STATEVAR={"NUMT":5, "COMPH":5, "OUT":5, "RAL":5, "CHR+":5 , "CHR-":5,  "MEM":5}
from Tkinter import  *
import os, sys, time, string, traceback
#import serial
# input is a file in clear text,  with RCCL instructions (look at help files html) 
#main classes are
#   model = the whole configuration of one model and the computation of the inputs->outputs
#   op = one operation = one line of the program
#   var one variable 
#   tx which is the hardw are, how many sw...
class error:
    def __init__ (self,  lineNumber,  text, line):
        self.lineNumber=lineNumber
        self.text=text
        self.line=line
    def prt(self):
        t="Error "
        if self.lineNumber >0:
            t=t+ "Line="+str(self.lineNumber)+"  "
        return t+self.text+"\n"+self.line
class model:
    def __init__ (self,  fichier):  # fichier object
        self.fichier=fichier 
        self.fichier.modeldata=[]
        self.vars={}# dictionnary of all the variables, indexed by the name(s)  of the variable
        self.dtrim={"DTRIM1":0,"DTRIM2":0, "DTRIM3":0, "DTRIM4":0 }# for the dtrims in the emulator 
        self.vars_N={}# the numerical vars, indexed with the index zone (index in the rccl table for the micro)
        self.vars_L={} #idem logical vars
        self.op=[]  # list of operations (objects op)
        self.LCDmessage={}  #dictionnary of error message from the BREAK
        self.err=[]      # error messages
        self.phase="" #name of the current phase
        self.log=[] #debug log
        self.modelData=[] # the data file for the model (from 1 or several files)
        self.tx=Tx()
        self.ser=0  #serial connection 
        self.avrdata= False #true if avr calc now  synchronized
        #___________________the parameters of the PPM signal
        self.PPM_ch =9  #number of transmitted channels 
        self.PPM_length=22.5 # length of a PPM frame, in msec
        self.mdxold=None  #the old version to compare
          
    def addvar(self, name, lineNumber, typeNum, typeOrig):
        # add a new var object to the list, if this is a new name, with the numerical type
        # for an existing name, add the reference and check that the type is the same
        if self.vars.has_key(name) ==False:
                    #add this  variable
                    v=var(name, typeNum, typeOrig, 0, self.err) #create the object "variable"
                    self.vars[v.name]=v   # create a link with the name 
        else:
            v= self.vars[name] #existing var
            if typeOrig==5:
                v.typeOrig=5; # sometimes the variable is initialized as normal and afterwards used as state variable
            if v.typeNum !=typeNum:
                self.err.append(error(  lineNumber ,"The type of the variable is changed ", line))
        return v # the variable instance        
    
    def open(self):
        #read the user file and fill the model with operation and variables
        try:
            lineNumber=0
            self.err=[]
            message="Open "+self.fichier.selectedModel
            self.fichier.getModel()
            for y in self.fichier.err:
                self.err.append(y)
            self.modelData=self.fichier.modelData
            self.listParams=self.fichier.getUpdateParam() #parameters from the transmitter
            self.op=[] # list of the operations
            # convert the file to  the list of op objects
            for line in self.modelData:          
                # line is the original line      
                lineNumber=lineNumber+1
                message = str(lineNumber)+"  "+line  # to print with a throwed error
                #print message  #debug
                x=cleanLine(line) # x=arguments list as received but the first * is added if needed
                outvar="*"
                invar=[] 
                if len(x)==0:continue
                if len(x)<2:
                    self.err.append(error(  lineNumber ,"To few arguments", line))
                    continue
                  #allocate several logical variables for one PHASE
                if x[1]=="PHASE":
                    # the full name is created  later , as a normal output var
                    # create the alternative clipped names
                    # example outvar= vol.aaa.bbbb is normally created in a later step 
                    # here, creation of logical variables vol.aaa and vol
                    p=x[0]
                    while True:
                        i = p.rfind(".") # find the lastr "."
                        if i >0 :
                            p=p[:i] # cut the last part
                            v = self.addvar(p, lineNumber, 0, 3) #add this logical variable
                            continue
                        break #nothing to cut
                        
                    
                #allocate  variables  
                if x[1]=="MODUL": #the modulation 
                    if len(x) <4:
                        self.err.append(error(  lineNumber ,"MODUL without the 2 parameters", line))
                        continue
                    if float(x[2]) <6 or float(x[2] )>12:
                        self.err.append(error(  lineNumber ,"MODUL channel number  6 ...12", line)) # check the Tx definitions too
                        continue
                    self.PPM_ch=int( x[2])
                    if float(x[3] )<16 or float(x[3]) >25:
                        self.err.append(error(  lineNumber ,"MODUL PPM length is incorrect", line)) # check the Tx definitions too
                        continue
                    self.PPM_length=float(x[3])
                    continue
                 
                if x[1]=="EQU":
                    # create ONE instance var with two keys in the dictionnary
                    if x[2][:1]!="@":
                        self.err.append(error(  lineNumber ,"EQU without @ variable", line))
                    typeNum= 1
                    if x[2][:2]=="@S": typeNum=0
                    v=self.addvar(x[0], lineNumber, typeNum, 0)
                    v.alias=x[2]
                    self.vars[v.alias]=v  # create a link  wtih the alias to the same object
                    continue
                if x[1][:5]=="DTRIM":
                    # the  var with the parameter should be already existing
                    #print "DTRIM  "+x[1][-1:]
                    if self.vars.has_key(x[2]) !=True :
                        self.err.append(error(  lineNumber ,"DTRIM parameter not declared", line))
                if x[1]=="BREAK":
                    if len(x)<2:
                        self.err.append(error(  lineNumber ,"BREAK error message is missing", line))
                    if lineNumber>254:
                        self.err.append(error(  lineNumber ,"BREAK error linenumber > 254", line))
                        
                if x[1]=="INIT" :
                    # check the format  (a : after the  name) normal processing of outvar follows
                    if x[0][-1:]!=":":
                        self.err.append(error(lineNumber, "Parameter without trailing : ", line))
                        continue                  
                if OUTVAR.has_key(x[1]) != True:    # opcode  is unknown
                    self.err.append(error(  lineNumber ,"Opcode unknown:"+x[1], line))
                    continue 
                if OUTVAR.get(x[1])> -1:# create the var instance for outvar
                    if x[0] =="": #missing outvar
                        self.err.append(error(  lineNumber ,"No output variable name", line))
                        continue
                    typeOrig=3 #normal variable, initialized to 0
                    if STATEVAR.has_key(x[1]) ==True:
                        if STATEVAR[x[1]]==5:
                            typeOrig=5
                    if x[0][:2]=="@V":
                    #if (x[1]=="OUTN" or x[1]=="OUTN" ): 
                        typeOrig=1
                    outvar=self.addvar(x[0], lineNumber, OUTVAR.get(x[1]), typeOrig)
                i = 1
                while i<len(x)-1:  # process the input arguments of this line -> fill op.invar
                    i=i+1
                    p=x[i] #input argument 
                    if x[1] == "BREAK" : #break with error message
                        if len(p)>16:
                            self.err.append(error(  lineNumber ,"BREAK message length  >16", line)) 
                        #add the error message to the list
                        self.LCDmessage[lineNumber]=p
                        continue                
                    try: # test if numeric
                        cx=float(p) # test if numeric
                        if x[1]=="INIT": # this "constant" is a variable, update is available by a digital trim or menu
                            #name="INIT_"+x[0]+str(lineNumber)
                            name="INIT_"+x[0]
                            namek=x[0]
                            if len(x)==4  :
                                name=name+x[3]
                                namek=namek+x[3]
                            v=self.addvar(name, lineNumber, 1, 6) #type=6
                            v.line=line
                            v.val=cx # this is a var instance with the value of the constant
                            #2010E  look at the new value in listParams[name]
                            if self.listParams.has_key(namek) ==True :
                                v.val =self.listParams[namek]
                            invar.append(v) # append this new var instance
                            continue
                        # others: numeric value but not INIT op
                        v=self.addvar(p, lineNumber, 1, 4)
                        v.val=cx # this is a var instance with the value of the constant
                        invar.append(v) # append this new var instance
                        continue
                    except: # this is not an error, it is a test "if numeric"
                    
                        if self.vars.has_key(p) ==True :
                            invar.append(self.vars[p]) # append this existing var instance
                            continue
                    if p[:1]=="@": # a @ input variable exists  but never declared as a outvar
                        typeNum= 1
                        typeOrig=0
                        if p[:2]=="@S"or p[:2]=="@B" or p[:2]=="@H": 
                            typeNum=0
                        if p[:2]=="@V": 
                            typeOrig=1
                        v=self.addvar(p, lineNumber, typeNum, typeOrig)
                        invar.append(v) # append this new var instance
                        continue
                    #if this input var does not exists ....
                    self.err.append(error(  lineNumber ,"input variable not declared as output "+p, line))
                    continue
                    
                # finally, add the operation
                operation= op(lineNumber, line, x[1], outvar, invar,  self.log)
#                if operation.opcode=="OUTN" or operation.opcode=="NUMT": #opcodes with a potential transition
#                    operation.slow=slow(0, 0) #nul duration slower, without effect 
                self.op.append(operation)       # add the operation 
        except:
            print message
            print  sys.exc_info()
            traceback.print_exc(file=sys.stdout)
        

    def calc(self,  check):
        # check is True for the first pass - to check errors
        NO_OP =["EQU"] # declarative op code which do no computation
        self.phase=""
        # reset of the variables
        for v in self.vars.values():
            if v.typeOrig==3 :# reset
                v.val=0 # correct reset for float,int or bool
        formerphase = self.phase
        phasedetected= False # to identify the first PHASE op  with a true value
#        servo_slower=0
        skip =0 # skip operations with a true IF( skip counts the eENDIFS)
        
        "______process the operation________________________"
        for x  in self.op :
            #conditionnal instructions
            #if check== True: print x.line
            if skip >0:
                if x.opcode=="ENDIF": skip=skip-1
                if x.opcode=="IF": skip=skip+1
                continue
            #skip is 0
            if  x.opcode== "IF"  and x.invar[0].val==False:
                skip=1
                continue
            if  x.opcode== "IF" or x.opcode== "ENDIF":continue
            if x.opcode in NO_OP : continue
            if x.opcode=="SLOW":
                if x.invar[0].val >20:
                    self.err.append(error(  x.lineNumber ,"SLOW maximum 20  (2sec)", x.line))
                    continue
            if x.opcode=="PHASE":
                thisIsTheActive=False
                if len(x.invar)==0 and phasedetected==False: #last phase without variable 
                    thisIsTheActive=True
                if len(x.invar)>0    :
                    if x.invar[0].val== True and phasedetected==False: # this is the active phase 
                        thisIsTheActive=True
                if thisIsTheActive==True:    
                    phasedetected=True
                    outPhases=x.outvar.name.split(".")
                    self.phase= x.outvar.name
                    i=1
                    p=outPhases[0]
                    self.vars[p].val=True
                    while i < len(outPhases): #each component of the phase name
                        p=p+"."+outPhases[i]
                        self.vars[p].val=True
                        i=i+1
                    continue 
                if len(x.invar)==0 :continue
                if x.invar[0].val== False: 
                    continue
#                if check ==False and self.phase!=formerphase and len(x.invar)>1: #phase with transition time
#                        servo_slower=x.invar[1] # duration of the transition -to be used by OUTN
                continue
            if x.opcode=="INIT":
                narg=  len(x.invar)
#               if narg==2  or  narg==4:  # x variable
                if narg==2  :  # x variable
                    if x.invar[1].val==0 :     #x is false
                        continue
#                if narg>2: #extended INIT   ppp: INIT  val (x)  v y
#                     if x.invar[narg-1].val==1: #if y true
#                        x.invar[0].val= x.invar[0].val+ x.invar[narg-2].val   # val = val+v             
                x.outvar.val=x.invar[0].val
                continue
 
            if x.opcode[:5]=="DTRIM":
                if len(x.invar)!=1  :
                    self.err.append(error(  x.lineNumber ,"all arguments are needed", x.line))
                x.invar[0].val= self.dtrim[x.opcode]
                continue 
                 
            if x.opcode=="ADD" or x.opcode=="+":
                sum=0
                for v in x.invar:
                    sum=valcheck(sum+ v.val)
                x.outvar.val=sum
                x.prt()
                continue
            if x.opcode=="SUB" or x.opcode=="-":
                sum=0
                for v in x.invar:
                    sum=valcheck(sum- v.val)
                x.outvar.val=sum
                x.prt()
                continue
            if x.opcode=="=":
                x.outvar.val=x.invar[0].val
                x.prt()
                continue
            if x.opcode=="EXP":      #f(z) = z+ C*z/(1+(2z)^4) dérivée à l'origine f'(0)=C f(1)=1
                z=x.invar[0].val /100.0
                c=-x.invar[1].val /100.0
                y=z +  (c *z)/(1+(2.0*z)**4)
                x.outvar.val=valcheck(y*100.0)
                x.prt()
                continue
            if x.opcode=="MULT" or x.opcode=="*":    
                if check==True and (x.invar[1].val<-1 or x.invar[1].val > 1):
                    self.err.append(error(  x.lineNumber ,"-1 <multiplier <1", x.line))
                x.outvar.val=valcheck(x.invar[0].val*x.invar[1].val)
                x.prt()
                continue
            if x.opcode=="MULT%" :    
                if check==True and (x.invar[1].val<-100 or x.invar[1].val > 100):
                    self.err.append(error(  x.lineNumber ,"-100 <multiplier <100", x.line))
                x.outvar.val=valcheck(x.invar[0].val*x.invar[1].val / 100)
                x.prt()
                continue
            if x.opcode=="OUT" or x.opcode=="OUTN":
                if check ==False:
                    old_out= x.outvar.val # just in case there is a phase transition
                if len(x.invar)!=3  :
                    self.err.append(error(  x.lineNumber ,"all arguments are needed", x.line))
                    continue
                neutre=x.invar[1].val
                maxdev=x.invar[2].val    
                x.outvar.val = (neutre+ ((x.invar[0].val/100.0)*maxdev ) ) 
                x.prt() 
#                if x.opcode=="OUTN": # no slow
#                    x.slow=slow(0, 0)
#                if  servo_slower >0: # a phase transition in this processing cycle 
#                    x.slow=slow((old_out-x.outvar.val), servo_slower) # create the slower of this operation,after a phase change
#                x.outvar.val=x.outvar.val-x.slow.get()
                t=x.outvar.name 
                if x.outvar.alias != "": t=t+" ("+x.outvar.alias+")"
                t=t.ljust(15) +"=%4.1f" % x.outvar.val
                continue
            if x.opcode=="CURVE"  or x.opcode=="LINE"  or x.opcode=="TRAVEL":  #  rounding is not programmed
                # x = constants value ,  sorted 
                # y = any variable, or parameter..
                # check
                dxmin=0.01
                if x.opcode=="TRAVEL"  :
                    if x.invar[0].val < x.invar[1].val:
                        self.err.append(error(  x.lineNumber ,"TRAVEL : x < First point", x.line))
                        x.outvar.val=x.invar[2].val
                        continue
                    if x.invar[0].val >x.invar[len(x.invar)-2].val:
                        self.err.append(error(  x.lineNumber ,"TRAVEL : x > Last point", x.line))
                        x.outvar.val=x.invar[len(x.invar)-1].val
                        continue                    
                    
                if x.opcode=="CURVE"  or x.opcode=="LINE":
                    # minimum distance between two points is
                    dxmin=1
                    if  x.invar[1].val != -100:
                        self.err.append(error(  x.lineNumber ,"Curve : First x not -100", x.line))
                    if  x.invar[len(x.invar)-2].val != 100:
                        self.err.append(error(  x.lineNumber ,"Curve : Last x not +100", x.line))    
                ip=1
                while ip +2<len(x.invar):
                    if x.invar[ip+2].val<x.invar[ip].val + dxmin:
                        self.err.append(error(  x.lineNumber ,"Curve : x not ascending or distance too short", x.line))
                        continue
                    ip=ip+2
                # max length (look at C code )
                if len(x.invar) < 5:
                    self.err.append(error(  x.lineNumber ,"Curve : to few points", x.line))
                if len(x.invar)%2 !=1 :
                    self.err.append(error(  x.lineNumber ,"Curve : missing value x or y", x.line))
                    continue
                if len(x.invar)-1 > self.tx.max_curve:
                    self.err.append(error(  x.lineNumber ,"Curve :TOO MANY arguments limit="+str(self.tx.max_curve), x.line))
                vx=x.invar[0].val
                ip=1 #index of the xi immediatly under vx
                ix=1
                while  ip < len(x.invar):
                    if x.invar[ip].val <=vx :
                        ix=ip
                    ip=ip+2
                #interpolation between the points (vx1,vy1)(vx2,vy2)
                vx1= x.invar[ix].val
                if vx-vx1 > 0.01:
                    vx2=x.invar[ix+2].val
                    vy1=x.invar[ix+1].val
                    vy2=x.invar[ix+3].val
                    x.outvar.val= valcheck(vy1+ (vx-vx1)*(vy2-vy1)/(vx2-vx1))
                    x.prt()
                    continue
                else:
                    x.outvar.val= x.invar[ix+1].val
                    x.prt()
                    continue
            if x.opcode=="SERV":
                if len(x.invar)!=4  :
                    self.err.append(error(  x.lineNumber ,"all arguments are needed", x.line))
                    continue
                vx=  x.invar[0].val
                vl= x.invar[3].val# low
                vc=x.invar[2].val#center
                vh=x.invar[1].val#high
                if vx > 0.0:
                    x.outvar.val=valcheck( vc+((vh-vc)*vx/100.0))
                else:
                    x.outvar.val= vc-((vl-vc)*vx/100.0)
                x.prt()
                continue
            if x.opcode=="ADDS": # symetrical mixer
                x.outvar.val=valcheck(x.outvar.val+ (x.invar[0].val*x.invar[1].val/100.0))
                x.prt()
                continue
            if x.opcode=="SUBS": # symetrical mixer
                x.outvar.val=valcheck(x.outvar.val- (x.invar[0].val*x.invar[1].val/100.0))
                x.prt()
                continue    
            if x.opcode=="ADDAS": # asymetrical mixer
                vx=  x.invar[0].val
                if vx < 0.0:
                    x.outvar.val=valcheck(x.outvar.val+ (vx*x.invar[1].val/100.0))
                else:
                    x.outvar.val=valcheck(x.outvar.val+ (vx*x.invar[2].val/100.0))
                x.prt()
                continue
            if x.opcode=="SUBAS": # asymetrical mixer
                vx=  x.invar[0].val
                if vx < 0.0:
                    x.outvar.val=valcheck(x.outvar.val- (vx*x.invar[1].val/100.0))
                else:
                    x.outvar.val=valcheck(x.outvar.val- (vx*x.invar[2].val/100.0))
                x.prt()
                continue
            if x.opcode=="AND":    #logical AND
                out=True
                for p in x.invar:
                    if p.val==False: 
                        out=False
                        break
                x.outvar.val=out 
                x.prt()  
                continue     
            if x.opcode=="OR":    #logical OR
                out=False
                for p in x.invar:
                    if p.val==True: 
                        out=True
                        break
                x.outvar.val=out 
                x.prt()
                continue
            if x.opcode=="NOT":    #logical NOT
                if x.invar[0].val==True : x.outvar.val=False
                else: x.outvar.val=True
                x.prt()
                continue
            if x.opcode=="FALSE":    
                x.outvar.val=False
                x.prt()
                continue
            if x.opcode=="TRUE":    
                x.outvar.val=True
                x.prt()
                continue
            if x.opcode=="MEM":
                if x.invar[0].val==True : x.outvar.val=True
                continue
            if x.opcode=="COMP":
                if x.invar[0].val>x.invar[1].val:
                    x.outvar.val=True
                else:
                    x.outvar.val=False
                x.prt()
                continue
            if x.opcode=="NUM":        
                if x.invar[0].val==True:
                    x.outvar.val=100.0
                else:
                    x.outvar.val=-100.0
                x.prt()
                continue    
 
            if x.opcode=="CHR+":   
                x.outvar.val=x.outvar.val+0.01
                continue
            if x.opcode=="CHR-":   
                x.outvar.val=x.outvar.val-0.01
                continue
            if x.opcode=="BEEP":
                print "beep"
                continue
            if x.opcode=="BREAK":
                print "break ="+x.line
                continue
            if  x.opcode=="UPD":
                # no simulation :there is no update of the parameters 
                continue
            print "calc error unknown op code ="+x.opcode +" line="+str(x.lineNumber)
        if skip >0:
           # print "IF without ENDIF"
            self.err.append(error(  x.lineNumber ,"IF without ENDIF", x.line))
            
 
        
        
    def find(self, x): #find the hardware table index 
        # x=variable instance
        # returns offset in the table or -1 if not a hardware 
        if x.typeNum==0: h=self.tx.L_tx
        else: h=self.tx.N_tx
        z=x.name[1:]
        if z  in h:
            return h.index(z)
        z=x.alias[1:]
        if z in h:
            return h.index(z)
        return -1
    def export(self):
        # create a file *.md for the interpreter program on the micro 
        #create a file *.mdy  = .mdx fully expanded
        # create a .txt documentation file
        # #reads the  *.mdp
        # (the parameters value updated on the micro), update the variables AND the original .mdx source file !
        #print "Export Travel="+str(TRAVEL)
        phase_lib =[] #list of all the phase names , each component is a name and a logical variable index
        param_lib=[]
        mdc=open(self.fichier.getfilename(self.fichier.selectedModel,"md"), "w")
        mdp=open(self.fichier.getfilename(self.fichier.selectedModel,"mdp"), "w")
        mdt=open(self.fichier.getfilename(self.fichier.selectedModel,"txt"), "w") #expanded source file, for information only
        # export the expanded source file 
        for line in self.modelData:
            mdt.write(line)
        mdt.close()
        
        updateMdx= 0 # 1 if a INIT line is updated 
        # _________model name
        x=self.fichier.selectedModel.replace(".", "_")
        mdc.write("0 "+x+EOL) # one string with modelname
        #__________ size of the arrays  and assign a index value to each variable
        n_numH=len(self.tx.N_tx)                      #index of the first variable after the hardware vars
        n_logicalH=len(self.tx.L_tx)
        #print "Hardware Num vars=%i   Logical=%i\n"%(n_numH,n_logicalH )
        vv = self.vars.values()
        def compare(a, b):
            return cmp(a.name, b.name) 
        vv.sort(compare)
#        for v in vv:
#            print v.name,  v.prt(self)
        for v in vv: #empty value
            v.index=255 ; 
        #assign the hardware , the constants and state var immediatly after the hardware
        # purpose: each calc loop resets the vars to zéro after theses variables
        for v in vv:
            if self.find(v)>=0: #assigns the hardware variables (fixed index in the beginning)
                v.index=self.find(v)
            
            if v.typeOrig==4 or v.typeOrig==5 or v.typeOrig==6: #4=constant 5=variable with state 6=parameter value 
                if v.typeNum==1:
                    v.index=n_numH
                    n_numH=n_numH+1
                if v.typeNum==0:
                    v.index=n_logicalH
                    n_logicalH=n_logicalH+1
        
        n_num = n_numH
        n_logical=n_logicalH
        
        for v in vv: #count the variables
            if v.index <255:#already assigned
                continue
            if v.typeNum==0:
                v.index=n_logical
                n_logical=n_logical+1
                continue
            if v.typeNum==1:
                v.index=n_num
                n_num=n_num+1
                continue
            print "Export error variable type  var="+v.name
        
        mdc.write("1 %03i %03i %03i %03i %02i %5.2f" % (n_num, n_logical, n_numH, n_logicalH,  self.PPM_ch, self.PPM_length) +EOL )
       
        for v in vv:  #parameter values -> line type 7
            if v.typeNum==1 and v.typeOrig==6:
                #sync with the updated parameters  internal name INIT_xxxxx
                name=v.name[5:]
                mdc.write("7 %03i %8i" % (v.index, v.val*TOINT)+"#"+v.name+EOL )
                mdp.write("2 %i %i %s" % (v.index, v.val*TOINT, name)+EOL )
        for v in vv:   #constants  -> line type 2
            if v.typeNum==1 and (v.typeOrig==4 or v.typeOrig==5):
                mdc.write("2 %03i %8i" % (v.index, v.val*TOINT)+"#"+v.name+EOL )        
    
        for op in self.op:  #operations line type 3/4
            if OPCODE.has_key(op.opcode) != True:
                print "Error export for opcode="+op.opcode
            # opcode with a special variable type 
            if op.opcode=="EQU": continue
            if op.opcode=="PHASE":
                #fist arg is the input var (or 255 if empty)
                if len(op.invar)>0:
                    mdc.write("3 "+OPCODE[op.opcode]+" "+op.invar[0].getindexT(True))
                else:
                    mdc.write("3 "+OPCODE[op.opcode]+" "+"255")  # 255  is a  empty argument
                # then the output vars
                outPhases=op.outvar.name.split(".")
                i=1
                p=outPhases[0]
                mdc.write (" "+self.vars[p].getindexT(True))
                pnum=self.vars[p].getindexT(True)
                while i < len(outPhases):   #each component of the phase name
                    p=p+"."+outPhases[i]
                    pnum=self.vars[p].getindexT(True)
                    mdc.write (" "+pnum)
                    i=i+1
                #mdc.write("\n")
                mdc.write("# %03i\n"%(op.lineNumber))                
                #the phase name
                #mdc.write("5 L"+pnum+" "+  p   +"\n") # the full phase name , for the LCD
                phase_lib.append( [p, pnum]  )
                continue
            # normal processing all variables have a type that the opcode knowns)
            if op.opcode=="INIT":
                mdc.write("4 "+OPCODE[op.opcode])
                mdc.write(" "+op.outvar.getindexT(True))
                #mdc.write(" "+op.invar[0].getindexT(True)) #value
                if op.invar[0].typeOrig==6: # normal - but a variable is another possibility
                    param_lib_txt=op.outvar.name
                    if len(op.invar)==2 or len(op.invar)==4: # condition
                        #mdc.write(" "+op.invar[1].getindexT(True))
                        param_lib_txt=param_lib_txt+op.invar[1].name
                    param_lib.append( [param_lib_txt, op.invar[0].getindexT(True)]  ) #param_lib is later used to write the "6"lines
                i=0
                while i<  len(op.invar):
                    mdc.write(" "+op.invar[i].getindexT(True)) #value
                    i=i+1
                mdc.write("# %03i\n"%(op.lineNumber))
                continue            
            
            
            if op.opcode[:5] =="DTRIM": # type 8 : assigne a DTRIM 
                mdc.write("8 "+op.invar[0].getindexT(True)+" "+op.opcode[-1:]+"\n")
                continue
            if op.opcode=="BREAK":
                mdc.write("3 "+OPCODE[op.opcode])
                mdc.write(" %03i\n"%(op.lineNumber))
                continue
            
            # other opcodes
            mdc.write("3 "+OPCODE[op.opcode])
            if isinstance(op.outvar, var): 
                mdc.write(" "+op.outvar.getindexT(True))
            for i in op.invar:
                mdc.write(" "+i.getindexT(True))
     
            mdc.write("# %03i\n"%(op.lineNumber))
        #write EOF
        #mdc.write("?                 \n")
        #write the names of phases and parameters and the BREAK error messages
        phase_lib.sort()
        for x in phase_lib:
            mdc.write("5 L"+x[1]+" "+x[0]+"\n")       
        param_lib.sort()
        for x in param_lib:
            mdc.write("6 N"+x[1]+" "+x[0]+"\n")          
        for x in self.LCDmessage.keys():
            mdc.write("9 B%03i "%x)
            mdc.write(self.LCDmessage[x] +"\n")
        #close
        mdc.close()  
        mdp.close()
        #___________construct the dictionnary index->var________________________________
        self.vars_N ={}
        self.vars_L ={}
        for v in self.vars.values():
            v.avrdata=False
            if v.typeNum==1: 
                self.vars_N[v.index]=v 
            else: 
                self.vars_L[v.index]=v 
                  

class fichier:
    # all the models and std are processed here in this+ file
    def __init__(self,  name="RCCLModels"):
        self.prePath = os.getcwd()+os.sep+"Models"+os.sep  #default path
        if len(sys.argv)>1:
            self.prePath= sys.argv[1]   
        self.name=name
        self.selectedModel="" # currently selected model for the simulation/ this is a  hierarchical name 
        self.modelData=[] # the selected model 
       # self.modelDataM=[]# copy of the model file
       #self.headerData=[]# all the header files for this model (first lines with STD
        self.err=[] #preprocessor errors
    def getfilename(self,name,  ext):
        # file = modelname with . replaced by _
        
        file=name.replace( ".", "_")
        return self.prePath+file+"."+ext
    def open(self): #open the main file
        x=self.getfilename(self.name,"mdx")
        self.file=open(x, "r")
    def close(self): #close the  file
         self.file.close()
    def getListModels(self): #get a list of models from the directory list
        listModels=[]
        for f in os.listdir(self.prePath) :
            if os.path.isfile(os.path.join(self.prePath, f)):
                (shortname, extension) = os.path.splitext(f) 
                if extension==".mdx" and shortname[:4]!="RCCL":
                    listModels.append(shortname)
                    #print "mdx File to process=", shortname
        listModels.sort()
        return listModels
    def getModel(self): 
        fincl=fichier(name = self.selectedModel)
        #fincl.mdpToMdl()
        fincl.open()
        self.modelData=[] # the selected model , with all the STD components
        self.err=[]
        #self.modelDataM=[]# copy of the model file
        lineNumber=0
        while  (True) :
            finclLine= fincl.file.readline()
            lineNumber = lineNumber + 1
            if len(finclLine)==0:
                break
            x = cleanLine(finclLine)
            if len(x) ==0:
                continue 
            if x[0] == "CALL"  or x[0] == "CALLP"  :
                #CALL -> simple module  where the INIT are defined in the module
                #CALL-> the internal INITs are replaced
                self.modelData.append("# *********"+finclLine)
                call_params = x  
                inits=[]
                if x[0]=="CALLP":
                    while (True):
                        finclLine= fincl.file.readline()
                        #print finclLine
                        y = cleanLine(finclLine)
                        if  len(y)>0 and y[0]=="END":
                            break
                        if  len(y)>0 and y[1]=="INIT":
                            inits.append(finclLine) #specific inits 
                            continue
                        if len(y) >0:
                            self.err.append(error(  lineNumber ,"CALLP not followed with INITs  + END ?"+defFile, line))
                            break
                #expand
                #print "Open def file ="+ call_params[1]
                for i in inits:
                    self.modelData.append(i) 
                    #print i
                defFile="Def"+os.sep+ call_params[1]
                try:
                    filedef=fichier(name = defFile)
                    filedef.open()
                except:
                    print  sys.exc_info()
                    self.err.append(error(  lineNumber ,"CALL : file not found ="+defFile, line))
                    continue               

                def_params=[]
                while (True):
                    line= filedef.file.readline()
                    if len(line)==0:
                        break
                    x=cleanLine(line)
                    if len(x)==0:
                        continue
                    if x[0]=="DEF":
                        def_params=x
                        if (len(call_params) -1) != len(def_params):
                            self.err.append(error(  lineNumber ,"CALL :"+defFile + " number of parameters is different from DEF", line))
                            break
                        continue
                    i =1
                    while (i< len(def_params)):
                        line = str.replace(line,  def_params[i],call_params[i+1] )
                        i=i+1
                    x=cleanLine(line)
                    lineToDelete = False
                    if x[1] =="INIT":
                        for element in inits:
                            p=cleanLine(element)
                            if p[0]==x[0]:
                                lineToDelete=True
                    if lineToDelete == False:
                        self.modelData.append(line) 
                        #print line
                filedef.close()   
                self.modelData.append("# ************  end of def file\n")
            else:
                self.modelData.append(finclLine)
                #print finclLine
        fincl.close()
  
 
    def getUpdateParam(self):
        # look at the file .mdp from the micro,with updated parameters
        #fn=self.prePath+self.selectedModel.replace(".", "_")+".mdp"
        listParams={}
        fn=self.getfilename(self.selectedModel,"mdp")
        if os.path.isfile(os.path.join(self.prePath, fn)):
            try:
                f=open(fn, "r")
                for line in f:
                    x=cleanLine(line) # can be used here too
                    # format 2 nnn vvvvv  xxxxxx  , traiter ici la valeur vvvv et le nom xxxxx
                    if x[0]!="2":
                        continue
                    i =int(x[1])
                    val=float(x[2])/TOINT
                    name = x[3]
                    #print ("getUpdateParam %i %s = %5.2f"% ( i , name, val))
                    listParams[name]=val
            except :
                #print "No file mdp ?"+fn
                print  sys.exc_info()
                ##traceback.print_exc(file=sys.stdout)
        return listParams #returns the file as a list

    def testerr(self, can, mdx, name):
        # displays the error messages returns True if errors
        err=""
        can.delete(ALL)
        if len(mdx.err )>0:
            err=name +"\n"
            
        for error in mdx.err :
            print error.prt()  # console print
            err=err+error.prt()+"\n"
        can.create_text(20, 20, text=err, anchor=NW,width=780) # canvas print
        if err=="": return False
        else: return True  
    
    
    def renewSd(self, can): # recalc and export each missing md or md older then mdx
        for f in os.listdir(self.prePath) :
            fullname=os.path.join(self.prePath, f)
            if os.path.isfile(fullname):
                (shortname, extension) = os.path.splitext(f) 
                if extension==".mdx" and shortname[:4]!="RCCL":
                    statinfomdx= os.stat(fullname)
                    #print shortname,  ".mdx", time.strftime("%d/%m/%Y %I:%M:%S %p",time.localtime(statinfomdx[8]))
                    if os.path.isfile(os.path.join(self.prePath, shortname)+".md"):
                        statinfomd= os.stat(os.path.join(self.prePath, shortname)+".md")
                        #print "  ", shortname,  ".md", time.strftime("%d/%m/%Y %I:%M:%S %p",time.localtime(statinfomd[8]))
                        if statinfomd[8] > statinfomdx[8]:                           
                            continue
                    #print "  ",shortname ,  "uptodate"
                    #recalc
                    #print "  ",shortname ,  "export md"
                    self.selectedModel=shortname
                    mdx= model(self)      
                    mdx.open()
                    self.testerr(can, mdx, shortname)
                    for error in mdx.err :
                        print error.prt()  # console print
                        return
                    
                    mdx.calc(True) # this is the initial validation
                    
                    mdx.export()
                    print "renew done for md File =", shortname
                    
#    def mdpToMdl(self): #transform the .mdp in the editable .mdl
#        xin=self.getfilename(self.name,"mdp")
#        fileMdp=open(xin, "r")
#        xout=self.getfilename(self.name,"mdl")
#        fileMdl=open(xout, "w")
#        while (True):
#            x = fileMdp.readline()
#            if len(x)==0:
#                break
#            z = x.split()
#            val = float(z[2])/TOINT
#            name=z[3]
#            fileMdl.write("%-30s   %7.2f\n" % (name, val))
#        fileMdl.close()
#        fileMdp.close()
        
            
        
        
        
class op:
    "One operation line"
    def __init__(self,  lineNumber,   line,opcode, outvar, invar, log):
        # line= original line from the text file     ,  linenumbrt provided for 
        # log = list of log messages        
        # invar input  parameters
        self.lineNumber=lineNumber
        self.line=line
        self.opcode=opcode
        self.outvar=outvar #the output variable
        self.invar=invar # argument list of parameters or input variables
        self.log=log
 
    def prt(self):
        if len(self.log)==0:return 
        t="(%4i)  " %   (self.lineNumber)
        t = t+" /"+ self.outvar.prt(self)+"  "+self.opcode+" "
  
#        for i in self.invar:
#            try:
#                t=t+" /"+ i.prt()
#            except:
#                t=t+" /"+str(i)
        self.log.append(t)          
        
class var:
    "Variable of the RC_model simulation program"
    def __init__(self,  name,   typeNum, typeOrig, typeArg, err):
        self.name=name    # name of the variable
        self.alias =""           #fixed name  , related to memory and io ports
        self.descr=""    # short description 
        self.typeNum=typeNum     #0=logique 1=numérique
        self.typeOrig=typeOrig  
            #0=hardware input @  1=hardware output  
            #2=paramètre  NO MORE USED
            #3=work variable 
            #4=constant , 5 variable with state, 6=parameter value 
        self.line="" #for typeOrig=6:full copy of the line with the INIT
            
        self.typeArg=typeArg                               #0= argument de l'opération 1=argument interne
        if typeNum==-1 and typeOrig==0 : #orig @ 
            self.typeNum=1
            if name[:2]=="@S": self.typeNum=0
        if self.typeNum==0: self.val=False  #logical variable
        if self.typeNum==1: self.val=0.0    # numerical variable, as float
        self.err = err  # pointer to the error list of the model clas s 
        self.index=0 # index in the array for the micro
        self.valAvr=0 #valeur calculée par le micro 
        self.avrdata= False #True si cette valeur existe
 

    def getindexT(self, t):
        # returns the code Vnnn formatted for export
        # t True if normal mode, without prefix (opcode knows the typeNum of each argument) -> output only the index
        # t false, output the full index letter+index
        if t==True: 
            return "%03i" % (self.index)
        else: # not used !
            if self.typeNum==0: t="L"
            if self.typeNum==1: t="N"
            return "%s%03i" % (t, self.index)
    def prt(self,  model):
        TYPENUM=["L", "N"]
        TYPEORIG=["@in        ", "@out       ", "Parameter  ", "Variable   ", "Constant   ", "State var  ","Param.value"]
        
        t=self.name 
        if self.alias=="": t=t+"      "
        else: t=t+" ("+self.alias+") "
        t=string.ljust(t, 30)
        #t=t+"("+str(self.typeOrig)+")"
        #t=t+TYPEORIG[self.typeOrig]+"   "
        t=t+TYPENUM[self.typeNum]
        if SIMUL>0:
            #t=t+str(self.index)+" "
            t=t+("%03i"%self.index)
        if self.avrdata ==True:
            t=t+("[%5.2f] "% self.valAvr)
        if self.typeNum==0:
            if self.val==True:
                return t+ " True"
            else:
                return t+" False"
            
        if type(self.val)==type(0): 
            return t+ "=%8i"% self.val
        if type(self.val)==type(0.1): 
            return t+ "=%8.2f"% self.val

        if type(self.val)==type(""): 
            return t+ "no value"
        return t+ " ??? Value="+str(self.val) 
        
        
class Tx():
    # this defines 
    # 1)the input for the simulation program = not the real transmitter
    # 2) the  index of each control of the real transmitter in the tables  (for the microprocessor)
            #this is shared by the C program of the emulator 
          
    def __init__(self):
        #__________the sticks  in the simulation program____________________________________________________
        # coding of a stick (manches)
        #  1 = the main stick @M1  ...M4
        #  2= the  trim   like  @T1 , @D1 or ""
        
        self.manches=[["@M1", "DTRIM1"], ["@M2", "DTRIM2"],["@M3", "DTRIM3"],["@M4", "DTRIM4"]]
        # the names DTRIMi are reserved for digital trims
        # ___how many controls are showed on the simulation window___________________________
        self.cursors = ["@C1", "@C2" , "@C3"]# how many other analog inputs
        self.sw  =["@BA","@S1","@S1","@S2", "@S3","@S6", "@HS", "@H1"]  # how many switches  2 positions
        self.sw3=["@S4", "@S5"]  # how many sw 3 positions ( seen as two  2 positions sw, S5a and S5b for instanc
        
        self.dtrims=[]  # how many other digital trims ???????????????
        #limits
        self.max_curve=30 # max points in a curve *2
        
        # __________the variables in the microprocessor ________________________________________________________
        # this table provides a equivalence between  the name of a hardware variable and the index of this variable in L_tx or N_tx
        # theses tables are the interface with the computational routine calc()
        # the variable defined here are
        #  -real hardware: ADC, switch...
        #  -fixed variable defined in the core program of rccl
        #       digital trims
        #       servo outputs
        # example: the variable @C1 is at the index 4 in the Numerical table
        #here (my prototype) there are 4 sticks, 3 sliders,   4 digital trims for the sticks ,max 12 servo channels
     
        self.N_tx=["M1", "M2", "M3", "M4", "C1", "C3", "C2", \
                    "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11","V12"]
        # ADC with 8 channels:analog inputs are the first 7 positions, the last is for battery monitoring
        # digital input  : switches and a special button
        
        self.L_tx=["H1","HS","BA","S1","S2","S3","S6","S4a","S4b","S5a","S5b", "B1"] # see io.c
        # H1 and HS are in the first 2 positions
        

def cleanLine(line): 
    # suppress comments, \n, replace tab with space    
    #returns a splitted list of the arguments
    lx=line
    x=[]
    if len(lx)<=2: return x # white line
    #suppress comments from #
    c=line.find("#")  
    if c==0:        return x # line with # in fist position = empty operation
    if c>0:        lx=lx[:c]
    # no newline and no empty line
    c=lx.find("\n") 
    if c>0:        lx=lx[:c] 
    # replace tab with space 
    lx=lx.replace("\t", " ")
    z = lx.split() # split at the space separator
    # some OPCODES without output variable
    # insert a *
    if len(z)==0 : return x
    if len(z)>1 and z[1] =="TRAVEL" :
        if TRAVEL ==True:
            return z
        else:
            return []
    if z[0] in ["IF","ENDIF","DTRIM1","DTRIM2","DTRIM3","DTRIM4", "BEEP", "BREAK", "SLOW", "MODUL", "INC", "UPD"]:
        # pull a * as first argument
        y=["*"]
        for item in z:
            y.append(item)
        return y
    else:
        return z
#class slow():
#    # to make a slow transition
#    def __init__(self,  amort_value, duration):
#        self.amort_value=amort_value # initial full correction to apply
#        self.time= time.time() # a timestamp, float , in seconds
#        self.duration=duration # duration of the correction, in sec
#    def get(self): # returns the correction (from initial value to zero during the duration)
#        if self.duration <0.001 :return 0.0 #empty slow
#        t=time.time()-self.time # elapsed time since transition
#        if t>self.duration: return 0.0 #elapsed duration
#        return self.amort_value* (1.0 - t/self.duration) # correction to subtract
        

def valcheck(outvar):
    MAX= 125
    if outvar < -MAX:
        return -MAX
    if outvar >MAX:
        return MAX
    return outvar


#_____________________________________________________________________________________
#if __name__ == '__main__':
#    for arg in sys.argv: 
#        print arg
#    f=fichier()
#    f.open()
#    z=f.getListModels()
#    print z
#    data=f.getModel()
#    print data
#
#
#    
#    # normal test open calc
#    f=fichier()
#    #f.selectedModel="planeur.test"
#    f.selectedModel="planeur.ThermikXL"
#    mdx=model(f)
#    mdx.open()
#    for error in mdx.err :
#        print error.prt()    
#    mdx.export()
#    for v in (mdx.vars.values()):
#       print  v.prt(mdx)
#    mdx.calc(True)
#    for v in (mdx.vars.values()):
#       print  v.prt(mdx)
#    

#   mdx= model("/mnt/datalin/d/Python/RCCL_v2/RCCL/Models/test.mdx") 
#   mdx.open()
#    
#   for error in mdx.err :
#       print error.prt()
#    
#    print "Phase="+mdx.phase
#    for v in (mdx.vars.values()):
#        print  v.prt(mdx)       
#    if len(mdx.err)==0:
#        mdx.calc(True)
#    pass
# test CURVE
#    x=-100
#    while x<101:
#        mdx.vars["@M1"].val=x
#        mdx.calc(True)
#        print "x="+str(x) +"   "+str(mdx.vars["z"].val)
#        x=x+10
 
 # test OR
#    mdx.vars["@S1"].val=True
#    mdx.vars["@S1"].val=True
#    mdx.vars["@S1"].val=False
#    mdx.calc(True)
#    print  mdx.vars["a"].val
#    print  mdx.vars["b"].val
 
if __name__ == '__main__':
    print "Main"
    k=[]
    for item in OUTVAR.keys(): 
        k.append(item) 
    k.sort ()
    for y in k:
       print y,  OUTVAR[y]
